ddae70a8bc
The first question about any change is which file it belongs in, and the three documents that
kept being asked to answer it - README (the tour), ROADMAP (what is done) and CHANGELOG (why it is
like that) - were never going to. docs/CODE_MAP.md answers it: all 74 source files with the purpose
taken from the KDoc already sitting next to them, their size, and the tests that cover them,
followed by every test name in the suite. Those names are sentences about behaviour in this project
("a batch rename is a single undo step"), so the index doubles as the specification, and it is the
cheapest way for a reader, human or otherwise, to find out whether something is already covered.
The map is generated by tools/make-code-map.py rather than written, because a hand-maintained
description of 74 files is wrong within a milestone and nobody notices. The script harvests
declarations and comments; --check fails the moment the file stops matching the tree, and it caught
this very change (350 tests to 355); and the file says so about itself in its first line. A file is
described by the KDoc of the declaration that shares its name, which is the convention this
codebase already follows, and where no declaration matches, the first KDoc is used and the file is
listed at the end - so 15 fallbacks out of 74 are visible rather than assumed. Test files are
attached by the class they declare (NameTest -> Name.kt), which is a name-based match and therefore
approximate: the one honest weakness, and worth knowing before trusting a row that says a file has
no tests.
AGENTS.md is the one page that points at the map: what the app is, the two files to read before the
source, the exact commands, the layout with its dependency direction (ui -> domain -> data -> core,
with core free of Android), the rules that have tests behind them, what to read on demand and what
not to read at all, the definition of done, and the traps that have cost real time here - MIUI's two
install gates, assembleDebug racing an adb install, R8 stripping what only a resource names, and adb
input text not delivering a slash.
The same pass made four of the project's stated rules fail instead of merely being stated, in
ProjectInvariantsTest, which reads the source tree: core/ imports no Android (the engine stays
testable without Robolectric), no Text(...) in ui/ is handed a string literal (text comes from
strings.xml, which the pseudo-locale build exercises), the manifest requests exactly the six
permissions the app is built around and never INTERNET, and separator handling lives in core/ alone.
Three of the four were already true. The fourth found a bug: createFolder checked only that the name
was not empty, so "notes/final" was resolved by StoragePath into the notes folder that happened to
exist - the dialog offered one folder in the folder on screen and the app created a different one.
Rename refused the same input, because the rule has exactly one implementation and only one of its
two callers used it.
Both callers now go through isUsableName, and the shared name dialog says why a name is refused -
"A name cannot contain a slash", while it is typed, with its button disabled - rather than appearing
to do nothing when it is pressed. The batch dialog and both single-name dialogs read the same
string, so the rule reads the same wherever a name is entered. Verified on the phone: a slash typed
into New folder shows the note with Create the only disabled node in the dump, and deleting the
slash clears the note and re-enables it.
355 JVM tests (5 added: the four invariants and the folder-name refusal), lint clean, the map
current. The source-level rules state their own limits in their comments - the literal rule catches
a literal as the whole text of a Text(...) and not one concatenated into resource text, because
seeing that would take a Kotlin parser, and a rule that pretends otherwise is worse than one that
says where it stops.
318 lines
18 KiB
XML
318 lines
18 KiB
XML
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
<!--
|
|
The Play listing name. Deliberately not "File Manager": dozens of apps are literally
|
|
called that, and a distinctive name is what makes the listing findable and
|
|
rememberable. "Plain" carries the pitch — no ads, no account, no clutter.
|
|
-->
|
|
<string name="app_name">Plain Files</string>
|
|
|
|
<!-- Progress notification for long file operations -->
|
|
<string name="operation_copy_title">Copying files</string>
|
|
<string name="operation_move_title">Moving files</string>
|
|
<string name="operation_delete_title">Deleting files</string>
|
|
<string name="operation_compress_title">Creating the archive</string>
|
|
<string name="operation_extract_title">Extracting files</string>
|
|
<string name="notification_channel_name">File operations</string>
|
|
<string name="notification_channel_description">Shows progress while files are being copied, moved or deleted</string>
|
|
<!-- Both numbers are counts of entries, so this stays a plain substitution. -->
|
|
<string name="notification_progress">%1$d / %2$d</string>
|
|
<string name="notification_preparing">Preparing…</string>
|
|
|
|
<!-- ===== Browser: storage access prompts ===== -->
|
|
<string name="browser_all_files_title">Full file access needed</string>
|
|
<string name="browser_all_files_body">To browse every folder, grant this app “All files access” in Settings. Without it you can still browse a folder you pick yourself.</string>
|
|
<string name="browser_all_files_action">Grant full access</string>
|
|
<string name="browser_tree_title">Choose a folder</string>
|
|
<string name="browser_tree_body">Without full access you can still work inside one folder you choose here. The grant is remembered across restarts.</string>
|
|
<string name="browser_tree_action">Choose folder</string>
|
|
<string name="browser_tree_granted">Folder added</string>
|
|
<string name="browser_tree_rejected">The folder could not be remembered</string>
|
|
|
|
<!-- ===== Browser: navigation ===== -->
|
|
<string name="browser_roots_title">Storage</string>
|
|
<string name="browser_up">Up</string>
|
|
<string name="browser_internal_storage">Internal storage</string>
|
|
<string name="browser_empty_folder">This folder is empty</string>
|
|
<string name="browser_no_matches">Nothing matches “%1$s”</string>
|
|
<string name="browser_free_space">%1$s free</string>
|
|
<plurals name="browser_item_count">
|
|
<item quantity="one">%1$d item</item>
|
|
<item quantity="other">%1$d items</item>
|
|
</plurals>
|
|
<string name="browser_search_hint">Search this folder</string>
|
|
<string name="browser_search_clear">Clear search</string>
|
|
<!-- The scope toggle: its label says what tapping it *does*, so it reads as an action. -->
|
|
<string name="search_scope_everywhere">Search everywhere</string>
|
|
<string name="search_scope_folder">Search this folder only</string>
|
|
<!-- %1$s is how many files have been seen so far, e.g. "12,340 files". -->
|
|
<string name="search_indexing">Indexing %1$s…</string>
|
|
<!-- %1$s is a timestamp, in the same unlocalised form the details sheet uses. -->
|
|
<string name="search_index_at">Index updated %1$s</string>
|
|
<string name="search_index_missing">Not indexed yet</string>
|
|
<string name="search_index_rebuild">Rebuild</string>
|
|
|
|
<!-- ===== Browser: libraries =====
|
|
The home screen's slices of the device by what a file is, rather than by which folder it
|
|
is in. Each opens an ordinary listing of the files the index knows of that kind. -->
|
|
<string name="library_section">Libraries</string>
|
|
<string name="library_images">Images</string>
|
|
<string name="library_videos">Videos</string>
|
|
<string name="library_audio">Audio</string>
|
|
<string name="library_documents">Documents</string>
|
|
<string name="library_archives">Archives</string>
|
|
<string name="library_recent">Recent</string>
|
|
<!-- Shown when a library has nothing to show. Deliberately not "This folder is empty": a
|
|
library is not a folder, and what is missing here is an index, not files. -->
|
|
<string name="library_empty">No files indexed here yet</string>
|
|
|
|
<!-- ===== Browser: duplicates =====
|
|
A scan compares file contents, so every line here is about what was read and what was
|
|
found, never about what a name or a date suggests. -->
|
|
<string name="browser_action_duplicates">Find duplicates</string>
|
|
<string name="duplicates_title">Duplicates</string>
|
|
<!-- %1$s and %2$s are counts. The two passes read different things, so they are not the same
|
|
sentence: one reads the beginning of every file, the other reads survivors in full. -->
|
|
<string name="duplicates_checking">Checking %1$s of %2$s…</string>
|
|
<string name="duplicates_comparing">Comparing %1$s of %2$s…</string>
|
|
<!-- The honest empty state: "found none" is only meaningful with how many were looked at. -->
|
|
<string name="duplicates_empty">No two files hold the same bytes</string>
|
|
<string name="duplicates_checked">Compared %1$s candidate files</string>
|
|
<string name="duplicates_unreadable">%1$s could not be read</string>
|
|
<string name="duplicates_nothing_checked">Nothing on the device shares a file size</string>
|
|
<!-- %1$s is a size, e.g. "1.2 GB". -->
|
|
<string name="duplicates_free">%1$s could be freed</string>
|
|
<!-- The action's label carries the count, because it deletes exactly that many files. -->
|
|
<string name="duplicates_action_delete">Delete %1$s</string>
|
|
<plurals name="duplicates_copies">
|
|
<item quantity="one">%1$d copy</item>
|
|
<item quantity="other">%1$d copies</item>
|
|
</plurals>
|
|
|
|
<!-- ===== Browser: actions ===== -->
|
|
<string name="browser_action_new_folder">New folder</string>
|
|
<string name="browser_action_sort">Sort</string>
|
|
<string name="browser_action_details">Details</string>
|
|
<string name="browser_action_copy">Copy</string>
|
|
<string name="browser_action_cut">Move</string>
|
|
<string name="browser_action_paste">Paste here</string>
|
|
<string name="browser_action_rename">Rename</string>
|
|
<string name="browser_action_delete">Delete</string>
|
|
<string name="browser_action_share">Share</string>
|
|
<string name="browser_action_open_with">Open with</string>
|
|
<string name="browser_action_select_all">Select all</string>
|
|
<string name="browser_action_clear_selection">Clear selection</string>
|
|
<string name="browser_action_undo">Undo</string>
|
|
|
|
<!-- ===== Browser: files another app has handed over ===== -->
|
|
<plurals name="incoming_save_here">
|
|
<item quantity="one">Save %1$d file into this folder</item>
|
|
<item quantity="other">Save %1$d files into this folder</item>
|
|
</plurals>
|
|
<string name="incoming_save_action">Save here</string>
|
|
<string name="incoming_pick_hint">Navigate to a file and tap it to hand it back</string>
|
|
<string name="incoming_cancel">Cancel</string>
|
|
|
|
<!-- ===== Trash ===== -->
|
|
<string name="trash_title">Trash</string>
|
|
<string name="trash_empty">Nothing in the trash</string>
|
|
<string name="trash_restore">Restore</string>
|
|
<string name="trash_restore_all">Restore all</string>
|
|
<!-- %1$s is the folder a trashed item will be put back into. -->
|
|
<string name="trash_restore_to">Restores to %1$s</string>
|
|
<string name="browser_action_trash">Trash</string>
|
|
<string name="browser_action_more">More options</string>
|
|
<string name="browser_action_refresh">Refresh</string>
|
|
<string name="browser_action_cancel">Cancel</string>
|
|
<string name="browser_action_create">Create</string>
|
|
<string name="browser_action_rename_confirm">Rename</string>
|
|
<string name="browser_action_delete_confirm">Delete</string>
|
|
<string name="browser_action_show_hidden">Show hidden files</string>
|
|
<string name="browser_view_mode">View</string>
|
|
<string name="browser_action_view_list">List view</string>
|
|
<string name="browser_action_view_grid">Grid view</string>
|
|
|
|
<!-- ===== Browser: sort ===== -->
|
|
<string name="browser_sort_by">Sort by</string>
|
|
<string name="browser_sort_name">Name</string>
|
|
<string name="browser_sort_size">Size</string>
|
|
<string name="browser_sort_date">Date</string>
|
|
<string name="browser_sort_kind">Type</string>
|
|
<string name="browser_sort_ascending">Ascending</string>
|
|
<string name="browser_sort_descending">Descending</string>
|
|
|
|
<!-- ===== Browser: new folder / rename ===== -->
|
|
<string name="browser_new_folder_title">New folder</string>
|
|
<string name="browser_new_folder_default">New folder</string>
|
|
<string name="browser_rename_title">Rename</string>
|
|
<!-- ===== Browser: batch rename =====
|
|
Renaming several files to one name with a number after each. The number is the point: a
|
|
batch that always started at one would be useless the second time it was used. -->
|
|
<plurals name="browser_rename_many_title">
|
|
<item quantity="one">Rename %1$d file</item>
|
|
<item quantity="other">Rename %1$d files</item>
|
|
</plurals>
|
|
<string name="browser_rename_many_start">Start at</string>
|
|
<!-- %1$s lists the first few names the pattern produces, e.g. "Holiday (10).jpg, Holiday (11).jpg". -->
|
|
<string name="browser_rename_many_preview">Will be renamed to %1$s</string>
|
|
<!-- %1$d is how many further names there are beyond the few shown. -->
|
|
<string name="browser_rename_many_more">… and %1$d more</string>
|
|
<!-- %1$s is a name the pattern asks for that something in this folder already holds. -->
|
|
<string name="browser_rename_many_taken">%1$s is already in this folder</string>
|
|
<string name="browser_rename_many_nothing">Every selected file already has that name</string>
|
|
<string name="browser_name_bad">A name cannot contain a slash</string>
|
|
<plurals name="message_renamed_many">
|
|
<item quantity="one">Renamed %1$d file</item>
|
|
<item quantity="other">Renamed %1$d files</item>
|
|
</plurals>
|
|
<!-- %1$d succeeded of %2$d attempted, when part of a batch could not be renamed. -->
|
|
<string name="message_renamed_some">Renamed %1$d of %2$d files</string>
|
|
<string name="browser_name_label">Name</string>
|
|
|
|
<!-- ===== Browser: details ===== -->
|
|
<string name="browser_details_title">Details</string>
|
|
<string name="browser_details_name">Name</string>
|
|
<string name="browser_details_location">Location</string>
|
|
<string name="browser_details_size">Size</string>
|
|
<string name="browser_details_kind">Type</string>
|
|
<string name="browser_details_modified">Modified</string>
|
|
<string name="browser_details_folder">Folder</string>
|
|
|
|
<!-- ===== Browser: wide-screen detail pane ===== -->
|
|
<string name="browser_detail_placeholder">Select an item to preview it</string>
|
|
<string name="browser_detail_loading">Loading…</string>
|
|
|
|
<!-- ===== File categories (the "Type" row of the details sheet) ===== -->
|
|
<string name="browser_kind_image">Image</string>
|
|
<string name="browser_kind_video">Video</string>
|
|
<string name="browser_kind_audio">Audio</string>
|
|
<string name="browser_kind_document">Document</string>
|
|
<string name="browser_kind_archive">Archive</string>
|
|
<string name="browser_kind_apk">App package</string>
|
|
<string name="browser_kind_text">Text</string>
|
|
<string name="browser_kind_other">File</string>
|
|
|
|
<!-- ===== Browser: delete + conflict ===== -->
|
|
<!--
|
|
A delete stages into the trash rather than removing, so this must not promise
|
|
permanence. It also must not promise an undo button: that lives in the snackbar and
|
|
is gone within seconds, whereas the trash keeps the item for a day.
|
|
-->
|
|
<string name="browser_delete_title">Move to the trash?</string>
|
|
<plurals name="browser_delete_body">
|
|
<item quantity="one">%1$d item will be moved to the trash, where it is kept for 24 hours before it is removed.</item>
|
|
<item quantity="other">%1$d items will be moved to the trash, where they are kept for 24 hours before they are removed.</item>
|
|
</plurals>
|
|
<string name="conflict_title">Items already exist here</string>
|
|
<plurals name="conflict_body">
|
|
<item quantity="one">%1$d item already has that name. What should happen?</item>
|
|
<item quantity="other">%1$d items already have that name. What should happen?</item>
|
|
</plurals>
|
|
<string name="conflict_replace">Replace</string>
|
|
<string name="conflict_keep_both">Keep both</string>
|
|
<string name="conflict_skip">Skip</string>
|
|
|
|
<!-- ===== Browser: messages ===== -->
|
|
<string name="message_folder_created">Folder created</string>
|
|
<string name="message_renamed">Renamed</string>
|
|
<string name="message_name_in_use">That name is already in use</string>
|
|
<plurals name="message_copied">
|
|
<item quantity="one">Copied %1$d item</item>
|
|
<item quantity="other">Copied %1$d items</item>
|
|
</plurals>
|
|
<plurals name="message_moved">
|
|
<item quantity="one">Moved %1$d item</item>
|
|
<item quantity="other">Moved %1$d items</item>
|
|
</plurals>
|
|
<!-- What a delete says now that it stages instead of removing: recoverable, not gone. -->
|
|
<plurals name="message_deleted_undo">
|
|
<item quantity="one">Moved %1$d item to the trash</item>
|
|
<item quantity="other">Moved %1$d items to the trash</item>
|
|
</plurals>
|
|
<plurals name="message_undone">
|
|
<item quantity="one">Put %1$d item back</item>
|
|
<item quantity="other">Put %1$d items back</item>
|
|
</plurals>
|
|
<plurals name="message_received">
|
|
<item quantity="one">Saved %1$d shared file</item>
|
|
<item quantity="other">Saved %1$d shared files</item>
|
|
</plurals>
|
|
<plurals name="message_failed">
|
|
<item quantity="one">%1$d item could not be processed</item>
|
|
<item quantity="other">%1$d items could not be processed</item>
|
|
</plurals>
|
|
<string name="message_cancelled">Cancelled</string>
|
|
<string name="message_saved">Saved</string>
|
|
<string name="message_save_failed">The file could not be saved</string>
|
|
<!-- Handing a file to another app, and the three reasons it can fail. -->
|
|
<string name="message_open_no_app">No app on this device can open this file</string>
|
|
<string name="message_open_unavailable">This file could not be handed to another app</string>
|
|
<string name="message_open_needs_full_access">Another app can only be given a file with full file access</string>
|
|
<!-- ...and the reasons a preview inside the app cannot be drawn. -->
|
|
<string name="message_preview_needs_full_access">This preview needs full file access</string>
|
|
<string name="message_preview_unreadable">This file could not be read</string>
|
|
<string name="message_archive_unreadable">This archive could not be read</string>
|
|
<!-- ...and why an entry inside an archive cannot be previewed. -->
|
|
<string name="message_preview_too_large">This file is too large to preview — extract it first</string>
|
|
<string name="message_preview_needs_extract">This file has no preview — extract it to open it in another app</string>
|
|
<string name="message_format_unsupported">This archive format is not supported</string>
|
|
<plurals name="message_compressed">
|
|
<item quantity="one">Compressed %1$d file</item>
|
|
<item quantity="other">Compressed %1$d files</item>
|
|
</plurals>
|
|
<plurals name="message_extracted">
|
|
<item quantity="one">Extracted %1$d file</item>
|
|
<item quantity="other">Extracted %1$d files</item>
|
|
</plurals>
|
|
|
|
<!-- ===== Archive + usage actions ===== -->
|
|
<string name="browser_action_compress">Compress to ZIP</string>
|
|
<string name="browser_action_extract">Extract here</string>
|
|
<string name="browser_action_disk_usage">Storage usage</string>
|
|
|
|
<!-- ===== Viewer / editor / usage ===== -->
|
|
<string name="browser_action_save">Save</string>
|
|
<string name="browser_action_close">Close</string>
|
|
<string name="browser_action_edit_text">Edit as text</string>
|
|
<string name="usage_title">Storage usage</string>
|
|
<string name="usage_empty">Nothing to measure here</string>
|
|
<plurals name="usage_file_count">
|
|
<item quantity="one">%1$d file</item>
|
|
<item quantity="other">%1$d files</item>
|
|
</plurals>
|
|
|
|
<!-- ===== Archive preview ===== -->
|
|
<string name="archive_empty">This archive is empty</string>
|
|
<string name="archive_extract_entry">Extract this file</string>
|
|
<string name="archive_preview_entry">Preview this file</string>
|
|
<!-- %1$s is an entry's uncompressed size inside the archive. -->
|
|
<string name="archive_entry_size_none">Not stored in the archive</string>
|
|
|
|
<!-- ===== PDF preview ===== -->
|
|
<!-- %1$d is the page being shown and %2$d how many there are. -->
|
|
<string name="pdf_page_of">Page %1$d of %2$d</string>
|
|
<string name="pdf_previous_page">Previous page</string>
|
|
<string name="pdf_next_page">Next page</string>
|
|
<string name="pdf_unreadable">This PDF could not be opened</string>
|
|
|
|
<!-- ===== Audio / video preview ===== -->
|
|
<string name="media_play">Play</string>
|
|
<string name="media_pause">Pause</string>
|
|
<string name="media_unplayable">This file could not be played</string>
|
|
|
|
<!-- ===== APK preview ===== -->
|
|
<string name="apk_package">Package</string>
|
|
<string name="apk_version">Version</string>
|
|
<string name="apk_min_android">Needs Android</string>
|
|
<string name="apk_target_android">Built for Android</string>
|
|
<string name="apk_permissions">Permissions</string>
|
|
<string name="apk_no_permissions">This app asks for no permissions</string>
|
|
<string name="apk_unreadable">This APK could not be read</string>
|
|
<string name="apk_loading">Reading the package…</string>
|
|
<!-- %1$d is an Android API level, the only number a manifest carries. -->
|
|
<string name="apk_api_level">API %1$d</string>
|
|
|
|
<!-- ===== Audio / video preview (continued) ===== -->
|
|
<!-- %1$s is the elapsed position and %2$s the total length. -->
|
|
<string name="media_position">%1$s / %2$s</string>
|
|
</resources>
|